ここでは、Lex が提供していない機能や一般にはあまり使われない機能を説明します。Flex はほぼ 100% Lex 互換ですが、Lex よりも後に実装されたため、性能的にもより優れており、また、広範な用途に使えるスキャナをより簡単に作成することができるよう特別な機能を提供しています。
多くの言語は、その識別子において大文字・小文字を区別しません(Pascal、BASIC、FORTRAN 等)。Lex にも、大文字・小文字を区別しないスキャナを指定するための方法がありますが、それらは概して美しくなく、理解するのも困難です。個々の文字を置き換えてくれる定義を長いリストにして作成することも可能ですし、すべての識別子を受け付ける1つのルールを作成し、そのルールにおいて大文字・小文字を変換してからトークンの種類を返すようにすることも可能です。以下のコードは、これら2つの方法を示すものです。定義を使うのであれば、以下のようになります。
%%
{B}{E}{G}{I}{N} return(BEGIN_SYM);
{E}{N}{D} return(END_SYM);
%%
{ALPHA}{ALPHANUM}* return(convert_and_lookup(yytext));
ほかにもこれと同じことをおこなう方法がありますが、いずれもエレガントではありません。
Flex はこの問題を簡単に解決するための方法を提供しています。コマンドラインで -i オプションを使うことによって、入力情報の大文字・小文字を区別しないスキャナを生成するよう Flex に対して通知することができます。これは、Flex では上記のようなテクニックを使う必要がないということを意味しています。例えば、
-i オプションには1つ注意すべき点があります。それは、スキャナが大文字・小文字を区別しないだけで、その変換まではしてくれないということです。このことは、Pascal においてシンボル名をハッシュしたいような場合、自分でシンボル名を大文字もしくは小文字に変換しなければならないことを意味しています。そうしないと、FOO と foo は異なるものとして扱われます。これは、シンボルを保存するルーチンの中で対処することもできますし、YY_USER_ACTION を使うことによって対処することもできます。これを実現する方法の例については、***ページの 4.1 節「Flex と C」における YY_USER_ACTION の説明を参照してください。
Flex の1つの問題に、どのルールを適用するかを決定する前に、入力情報中の次の1文字を先読みする必要があるということがあります。対話的ではない使い方をする場合にはこれは問題にはなりませんが、Flex を使ってユーザから直接入力文字を受け取るような場合には問題になることがあります。
このような場合を2つ挙げると、1つはシェルとやりとりする場合、もう1つはデータベースのフロント・エンドとやりとりする場合です。通常のアクションは、改行が入力の終わりを表わすというもので、改行自身は一種の「中身のない文」として受け付けるのが望ましいのですが、通常の Flex スキャナではこれは可能ではありません。Flex が常に先読みをするという事実は、改行が認識されるためにはユーザが次の行を入力しなければならないということを意味しています(すなわち、単一の改行は、それだけでは認識されず、他の文字が入力される必要があるということです)。これはシェル上ではまったく望ましくありません。
Flex にはこれを回避する方法があります。コマンドラインで -I オプションを使うと、Flex は必要な場合にしか先読みをしない特別な対話型スキャナを生成します。この種のスキャナはユーザから直接入力を受け取るのに適していますが、若干の性能劣化を引き起こすかもしれません。
注:-I オプションは、-f、-F、-Cf、-CF フラグと一緒に使うことはできません。このことは先読みができないことから来る性能劣化に加えて、パーサも性能向上のために最適化することができないということを意味しています。
-I オプションに関連するマイナス面は通常はきわめて小さいので、入力情報がどこから来るのか確かではなく、性能向上のための最適化を施す可能性を諦めても構わないのであれば、コマンドラインにおいて -I オプションを使う方が良いでしょう。
テーブルの圧縮とスピードの領域では、Flex は Lex の能力をはるかに上回っています。Flex
は、使われるオプションに応じて、Lex よりもはるかに高速なテーブル、あるいは、はるかに小さいテーブルを生成することができます。この節では、利用可能なオプションとそれらがスピードにもたらす影響について説明します。一般的には、テーブルが圧縮されるほど、そのスピードは遅くなります。Flex
では、これらのオプションはコマンドラインで与えられます。オプションは以下のとおりです。
-f、-Cf | このオプションは Flex がフル・テーブル(full table)を生成すべきであることを指定します。このテーブルはまったく圧縮されず、サイズが大きくなりますが、スピードは速くなります。これらのオプションが指定された場合は、アクションの部分に
REJECT を使うことはできない点に注意してください。
注:-f フラグと -F フラグは、Flex が生成するテーブルにおいて相違をもたらします。-f
フラグはフル・テーブル(full table)を生成し、-F フラグはファスト・テーブル(fast
table)を生成します。ファスト・テーブルとは、スピードを最大限にするよう最適化されたテーブル形式であり、一方、フル・テーブルには最適化は一切施されません。もたらされる結果はよく似ていますが、テーブルのサイズは大きく異なるものになる可能性があります。
|
-F、-CF | このオプションはファスト・テーブル(fast table)形式を用いてテーブルを生成するよう
Flex に通知します。一般的には、このテーブルのスピードは先に説明したフル・テーブル(full
table)とほとんど同じですが、使われるパターンに応じて、そのサイズはより小さくもより大きくもなる可能性があります。原則として、すべての識別子をキャッチするルールのほかにキーワードの一覧も持つファイルに対しては、-f
オプションを使うべきです。例えば、
NUM [0-9] ALPHANUM {ALPHA}|{NUM} %% begin return(BEGIN_SYM); ... ルールとアクション ... end return(END_SYM); {ALPHA}{ALPHANUM}* return(IDENTIFIER);
return(lookup(yytext));} |
-Ce | このオプションを使うと、性能にはわずかしか影響を及ぼさずに、テーブルのサイズをかなり減少させることができます。-Ce
が使われると、Flex は同等クラス(equivalence class)を作成します。同等クラスとは同一の方法で使われる文字のグループです。例えば、使われる数字が集合
[0-9] の範囲に限定されるのであれば、0 から 9 までの数は同等クラスの中に置かれることになります。
|
-Cfe、-CFe | 同等クラス(equivalence class)を持つファスト・テーブル(fast table)。このオプションによって生成されたスキャナもまた高速であり、かつ、-Cf
あるいは -CF を指定して生成されたスキャナと比較してサイズもはるかに小さくなる可能性があります。サイズもしくはスピードの一方が他方に比べてはるかに重要であるということがないのであれば、これは良い組み合わせです。
|
-Cm | これは Flex に対してメタ同等クラス(meta-equivalence class)を使うよう通知します。これは、一緒に使われることが多い文字の集合、もしくは(同等クラスが使われている場合には)同等クラスです。同等クラスが使われた場合は性能はさらに悪くなりますが、これは多くの場合、テーブル・サイズを小さくするのに非常に効果的な方法です。
|
-Cem | デフォルトのテーブル圧縮。このオプションで生成されるスキャナは、Flex
が生成するスキャナの中で事実上最も小さく、かつ、最も性能の劣るものになります。
|
-C | -C オプション単体では、同等クラス(equivalence class)、メタ同等クラス(meta-equivalence
class)を使わずにテーブルを圧縮するよう Flex に対して通知します。
|
Flex のデフォルトの動作は、コマンドライン上で -Cem オプションを使った場合に相当します。これは圧縮を最大限におこなうことになり、一般的には最も遅いスキャナが生成されることになります。こうした小さなテーブルはより速く生成され、コンパイルもより速く実行されるので、このデフォルトは開発段階では非常に便利です。スキャナのデバッグが終了した後は、より高速な(そして通常はよりサイズの大きい)スキャナを作成することができます。
翻訳テーブルは、文字をグループにマップするのに使われます。このテーブルは Lex の持つ機能の1つですが、POSIX では定義されていません。Flex でも翻訳テーブルを使うことはできますが、これはサポート対象外の機能です。Flex においては翻訳テーブルは不要です。というのは、Flex には -i オプションによる同等クラス(equivalence class)というものがあり、これが翻訳テーブルと同等の機能を実現しているからです(***ページの 5.1.1 節「-i オプション」を参照)。翻訳テーブルの機能は、互換性のためだけに存在する余分な機能です。翻訳テーブルを使うことはお勧めできません。翻訳テーブルを使いたいのであれば、それは定義ファイルの先頭の定義セクションにおいて定義されなければなりません。
翻訳テーブルの一般的な形式は以下のとおりです。
スキャナが複数のファイルからの入力を処理することができるということが必要になる状況はたくさんあります。例えば、多くの Pascal の実装ではコンパイル時に複数のファイルを取り込むことを許していますし、C ではスキャナもしくはプリプロセッサが #include 文を処理できなければなりません。このことが意味しているのは、スキャナは、カレントなスキャン処理のコンテキストを保存してから新しいコンテキストに変更し、その後に以前の状態と完全に一致する状態に復帰することができなければならないということです。
Flex スキャナは、スキャン処理のコンテキストを維持するために余分の処理が必要になるような、大きな入力バッファを使っています。しかし Flex は、複数の入力バッファの作成、切り替え、削除が非常に簡単におこなえるような特別な機能を提供しています。
Flex は、複数の入力バッファを取り扱うために、以下のような関数やマクロを提供しています。
YY_BUFFER_STATE yy_create_buffer(FILE *file, int size) | file で指定されるファイルのために、size で指定される数の文字を格納するのに十分な大きさのバッファを作成します。この関数は、後に複数のバッファ間の切り替え、新規に作成されたバッファの削除に使うことのできるハンドルを返します。
|
YY_BUF_SIZE | デフォルトのバッファ・サイズを定義するマクロです。yy_create_buffer()
に渡すべきサイズが分からない場合に、これを使うことができます。
|
void yy_switch_to_buffer(YY_BUFFER_STATE new_buffer) | バッファを切り替えます。次に読み込まれるトークンは new_buffer
で指定されるバッファから取られます。ファイルの終端(EOF)に達するか、次に
yy_switch_to_buffer() が呼び出されるまで、new_buffer からトークンが読み込まれます。new_buffer
が EOF に達すると、新しいバッファに切り替えることができます。
|
void yy_delete_buffer(YY_BUFFER_STATE buffer) | buffer で指定されるバッファを削除し、それに割り当てられたメモリを解放します。
|
YY_CURRENT_BUFFER | 使用中のカレントなバッファを返すマクロです。
|
複数のバッファを使うというアイデアを理解するための手助けとして、インクルードすべきファイルを探す C のスキャナの一部を以下に示します。これは C の #include のうち引用符で囲まれた文字列のみを受け付けます。例えば、
%{
#define MAX_NEST 10
YY_BUFFER_STATE include_stack[MAX_NEST];
int
include_count = -1;
%}
%x INCLUDE
%%
^"#include"[ \t]*\" BEGIN(INCLUDE);
<INCLUDE>\"
BEGIN(INITIAL);
<INCLUDE>[^\"]+ { /* インクルード・ファイルの名前を獲得する
*/
include_stack[++include_count] = YY_CURRENT_BUFFER;
yyin = fopen( yytext, "r" );
if ( ! yyin ){
yy_switch_to_buffer(
yy_create_buffer(yyin,YY_BUF_SIZE));
BEGIN(INITIAL);
注:<<EOF>> 機能は次の節で説明されます。<<EOF>> が何であり、何を行うものであるかについてのより詳細な議論については、***ページの 3.8 節「スタート状態」を参照してください。
ファイルの終端(EOF)が見つかると Flex は yywrap() を呼び、ほかに処理できる状態のファイルが存在するか調べます。yywrap() が 0 以外の値を返すと、もうこれ以上ファイルはないということを示し、したがって、これがまさに入力の最後であるということになります。状況によっては、この時点でさらに処理を行う必要のある場合があります(例えば、入力のために別のファイルをセットアップしたいということがあるかもしれません)。このような場合のために Flex は <<EOF>> 演算子を提供しています。これを使うことで、EOF が見つかった時に実行すべきことを定義することができます。***ページの 5.5.2 節「複数バッファを使う実例」を参照してください。そこには、EOF ルールを使って、終わりのないコメントやインクルードされているファイルの終端を見つける、良い例が示されています。
<<EOF>> 演算子の使用にはいくつか制限があります。それらを以下に示します。
EOF ルールはスタート状態とのみ一緒に使うことができます。スタート状態が指定されていない場合(すなわち、EOF
ルールが状態により制限されない場合)、<<EOF>> が使われていないすべての(排他的スタート状態を含む)状態が影響を受けます。このことは、
1つ注意しなければならない点は、EOF ルールは入力の最後で呼び出されるという点です。したがって、EOF
ルールのアクションは以下のいずれかを実行しなければなりません。
Copyright (C) 1992, 1993 Free Software Foundation
Permission is granted to make and distribute verbatim copies of this manual provided the copyright notice and this permission notice are preserved on all copies.
Permission is granted to copy and distribute modified versions of this manual under the conditions for verbatim copying, provided that the entire resulting derived work is distributed under the terms of a permission notice identical to this one.
Permission is granted to copy and distribute translations of this manual into another language, under the above conditions for modified versions, except that this permission notice may be stated in a translation approved by the Free Software Foundation.
日本語訳:市川和久
Japanese translation by Kazuhisa Ichikawa (ki@home.email.ne.jp)